3 * Block restriction interface.
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
23 namespace MediaWiki\Block
;
25 use MediaWiki\Block\Restriction\PageRestriction
;
26 use MediaWiki\Block\Restriction\Restriction
;
27 use Wikimedia\Rdbms\IResultWrapper
;
28 use Wikimedia\Rdbms\IDatabase
;
30 class BlockRestriction
{
33 * Retrieves the restrictions from the database by block id.
36 * @param int|array $blockId
37 * @param IDatabase|null $db
38 * @return Restriction[]
40 public static function loadByBlockId( $blockId, IDatabase
$db = null ) {
41 if ( is_null( $blockId ) ||
$blockId === [] ) {
45 $db = $db ?
: wfGetDb( DB_REPLICA
);
47 $result = $db->select(
48 [ 'ipblocks_restrictions', 'page' ],
49 [ 'ir_ipb_id', 'ir_type', 'ir_value', 'page_namespace', 'page_title' ],
50 [ 'ir_ipb_id' => $blockId ],
53 [ 'page' => [ 'LEFT JOIN', [ 'ir_type' => PageRestriction
::TYPE_ID
, 'ir_value=page_id' ] ] ]
56 return self
::resultToRestrictions( $result );
60 * Inserts the restrictions into the database.
63 * @param Restriction[] $restrictions
66 public static function insert( array $restrictions ) {
67 if ( empty( $restrictions ) ) {
72 foreach ( $restrictions as $restriction ) {
73 if ( !$restriction instanceof Restriction
) {
76 $rows[] = $restriction->toRow();
79 if ( empty( $rows ) ) {
83 $dbw = wfGetDB( DB_MASTER
);
86 'ipblocks_restrictions',
94 * Updates the list of restrictions. This method does not allow removing all
95 * of the restrictions. To do that, use ::deleteByBlockId().
98 * @param Restriction[] $restrictions
101 public static function update( array $restrictions ) {
102 $dbw = wfGetDB( DB_MASTER
);
104 $dbw->startAtomic( __METHOD__
);
106 // Organize the restrictions by blockid.
107 $restrictionList = self
::restrictionsByBlockId( $restrictions );
109 // Load the existing restrictions and organize by block id. Any block ids
110 // that were passed into this function will be used to load all of the
111 // existing restrictions. This list might be the same, or may be completely
114 $blockIds = array_keys( $restrictionList );
115 if ( !empty( $blockIds ) ) {
116 $result = $dbw->select(
117 [ 'ipblocks_restrictions' ],
118 [ 'ir_ipb_id', 'ir_type', 'ir_value' ],
119 [ 'ir_ipb_id' => $blockIds ],
124 $existingList = self
::restrictionsByBlockId(
125 self
::resultToRestrictions( $result )
130 // Perform the actions on a per block-id basis.
131 foreach ( $restrictionList as $blockId => $blockRestrictions ) {
132 // Insert all of the restrictions first, ignoring ones that already exist.
133 $success = self
::insert( $blockRestrictions );
135 // Update the result. The first false is the result, otherwise, true.
136 $result = $success && $result;
138 $restrictionsToRemove = self
::restrictionsToRemove(
139 $existingList[$blockId] ??
[],
143 // Nothing to remove.
144 if ( empty( $restrictionsToRemove ) ) {
148 $success = self
::delete( $restrictionsToRemove );
150 // Update the result. The first false is the result, otherwise, true.
151 $result = $success && $result;
154 $dbw->endAtomic( __METHOD__
);
160 * Updates the list of restrictions by parent id.
163 * @param int $parentBlockId
164 * @param Restriction[] $restrictions
167 public static function updateByParentBlockId( $parentBlockId, array $restrictions ) {
168 // If removing all of the restrictions, then just delete them all.
169 if ( empty( $restrictions ) ) {
170 return self
::deleteByParentBlockId( $parentBlockId );
173 $parentBlockId = (int)$parentBlockId;
175 $db = wfGetDb( DB_MASTER
);
177 $db->startAtomic( __METHOD__
);
179 $blockIds = $db->selectFieldValues(
182 [ 'ipb_parent_block_id' => $parentBlockId ],
188 foreach ( $blockIds as $id ) {
189 $success = self
::update( self
::setBlockId( $id, $restrictions ) );
190 // Update the result. The first false is the result, otherwise, true.
191 $result = $success && $result;
194 $db->endAtomic( __METHOD__
);
200 * Delete the restrictions.
203 * @param Restriction[]|null $restrictions
204 * @throws MWException
207 public static function delete( array $restrictions ) {
208 $dbw = wfGetDB( DB_MASTER
);
210 foreach ( $restrictions as $restriction ) {
211 if ( !$restriction instanceof Restriction
) {
215 $success = $dbw->delete(
216 'ipblocks_restrictions',
217 // The restriction row is made up of a compound primary key. Therefore,
218 // the row and the delete conditions are the same.
219 $restriction->toRow(),
222 // Update the result. The first false is the result, otherwise, true.
223 $result = $success && $result;
230 * Delete the restrictions by Block ID.
233 * @param int|array $blockId
234 * @throws MWException
237 public static function deleteByBlockId( $blockId ) {
238 $dbw = wfGetDB( DB_MASTER
);
240 'ipblocks_restrictions',
241 [ 'ir_ipb_id' => $blockId ],
247 * Delete the restrictions by Parent Block ID.
250 * @param int|array $parentBlockId
251 * @throws MWException
254 public static function deleteByParentBlockId( $parentBlockId ) {
255 $dbw = wfGetDB( DB_MASTER
);
256 return $dbw->deleteJoin(
257 'ipblocks_restrictions',
261 [ 'ipb_parent_block_id' => $parentBlockId ],
267 * Checks if two arrays of Restrictions are effectively equal. This is a loose
268 * equality check as the restrictions do not have to contain the same block
272 * @param Restriction[] $a
273 * @param Restriction[] $b
276 public static function equals( array $a, array $b ) {
277 $filter = function ( $restriction ) {
278 return $restriction instanceof Restriction
;
281 // Ensure that every item in the array is a Restriction. This prevents a
282 // fatal error from calling Restriction::getHash if something in the array
283 // is not a restriction.
284 $a = array_filter( $a, $filter );
285 $b = array_filter( $b, $filter );
287 $aCount = count( $a );
288 $bCount = count( $b );
290 // If the count is different, then they are obviously a different set.
291 if ( $aCount !== $bCount ) {
295 // If both sets contain no items, then they are the same set.
296 if ( $aCount === 0 && $bCount === 0 ) {
300 $hasher = function ( $r ) {
301 return $r->getHash();
304 $aHashes = array_map( $hasher, $a );
305 $bHashes = array_map( $hasher, $b );
310 return $aHashes === $bHashes;
314 * Set the blockId on a set of restrictions and return a new set.
317 * @param int $blockId
318 * @param Restriction[] $restrictions
319 * @return Restriction[]
321 public static function setBlockId( $blockId, array $restrictions ) {
322 $blockRestrictions = [];
324 foreach ( $restrictions as $restriction ) {
325 if ( !$restriction instanceof Restriction
) {
329 // Clone the restriction so any references to the current restriction are
330 // not suddenly changed to a different blockId.
331 $restriction = clone $restriction;
332 $restriction->setBlockId( $blockId );
334 $blockRestrictions[] = $restriction;
337 return $blockRestrictions;
341 * Get the restrictions that should be removed, which are existing
342 * restrictions that are not in the new list of restrictions.
344 * @param Restriction[] $existing
345 * @param Restriction[] $new
348 private static function restrictionsToRemove( array $existing, array $new ) {
349 return array_filter( $existing, function ( $e ) use ( $new ) {
350 foreach ( $new as $restriction ) {
351 if ( !$restriction instanceof Restriction
) {
355 if ( $restriction->equals( $e ) ) {
365 * Converts an array of restrictions to an associative array of restrictions
366 * where the keys are the block ids.
368 * @param Restriction[] $restrictions
371 private static function restrictionsByBlockId( array $restrictions ) {
372 $blockRestrictions = [];
374 foreach ( $restrictions as $restriction ) {
375 // Ensure that all of the items in the array are restrictions.
376 if ( !$restriction instanceof Restriction
) {
380 if ( !isset( $blockRestrictions[$restriction->getBlockId()] ) ) {
381 $blockRestrictions[$restriction->getBlockId()] = [];
384 $blockRestrictions[$restriction->getBlockId()][] = $restriction;
387 return $blockRestrictions;
391 * Convert an Result Wrapper to an array of restrictions.
393 * @param IResultWrapper $result
394 * @return Restriction[]
396 private static function resultToRestrictions( IResultWrapper
$result ) {
398 foreach ( $result as $row ) {
399 $restriction = self
::rowToRestriction( $row );
401 if ( !$restriction ) {
405 $restrictions[] = $restriction;
408 return $restrictions;
412 * Convert a result row from the database into a restriction object.
414 * @param \stdClass $row
415 * @return Restriction|null
417 private static function rowToRestriction( \stdClass
$row ) {
418 switch ( $row->ir_type
) {
419 case PageRestriction
::TYPE_ID
:
420 return PageRestriction
::newFromRow( $row );